package com.lib.utils;

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.Log;

import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * adb开启日志：adb shell setprop log.tag.CFLog_ DEBUG
 * adb开启日志写入文件：adb shell setprop log.tag.CFLog_Save DEBUG
 */
public class XLog {
    /* 显示上一个方法名 */
    public static final int INDEX_PRE_METHOD = 5;
    /* 最大保存文件条数 */
    private static final int FILES_LENGTH = 10;
    /* TAG */
    private static String TAG;
    private static String TAG_TO_FILE;
    /* 是否显示日志 */
    private static boolean sDebug;
    /* 是否将日志写入文件 */
    private static boolean sLogToFile;
    /* 日志文件路径 */
    private static File sLogSaveDir;

    public static Context sContext;
    private static volatile LinkedBlockingQueue<Runnable> sWorkQueue;
    private static volatile ExecutorService sExecutor;

    private static final ThreadLocal<Map<String, SimpleDateFormat>> SDF_THREAD_LOCAL = new ThreadLocal<Map<String, SimpleDateFormat>>() {
        @Override
        protected Map<String, SimpleDateFormat> initialValue() {
            return new HashMap<>();
        }
    };

    @SuppressLint("SimpleDateFormat")
    public static SimpleDateFormat getSafeDateFormat(String pattern) {
        Map<String, SimpleDateFormat> sdfMap = SDF_THREAD_LOCAL.get();
        //No Inspection ConstantConditions
        SimpleDateFormat simpleDateFormat = sdfMap.get(pattern);
        if (simpleDateFormat == null) {
            simpleDateFormat = new SimpleDateFormat(pattern);
            sdfMap.put(pattern, simpleDateFormat);
        }
        return simpleDateFormat;
    }

    public static boolean init(Context context, String prefix, boolean debug) {
        TAG = prefix + "Log_";
        TAG_TO_FILE = prefix + "Log_Save";
        sContext = context.getApplicationContext();

        if (debug) {
            sDebug = true;
        } else {
            sDebug = Log.isLoggable(TAG, Log.DEBUG);
        }
        sLogToFile = Log.isLoggable(TAG_TO_FILE, Log.DEBUG);

        if (sDebug) {
            // LLLog.setLogLs(new ILog());
        }
        XLog.i("显示日志：" + sDebug + "\n写入文件：" + sLogToFile);
        createEx();
        return sDebug;
    }

    private static void createEx() {
        if (sLogToFile && sExecutor == null) {
            synchronized (XLog.class) {
                if (sExecutor == null) {
                    sWorkQueue = new LinkedBlockingQueue<>(9999);
                    sExecutor = new ThreadPoolExecutor(1, 1, 30L, TimeUnit.SECONDS, sWorkQueue);

                    sLogSaveDir = FileUtils.getFilesDir(sContext, "Logs");

                    boolean mkdirs = sLogSaveDir.mkdirs();
                    if (!mkdirs) {
                        deleteOldestFile(sLogSaveDir);
                    }
                }
            }
        }
    }

    public static void i(String msg) {
        i(msg, INDEX_PRE_METHOD);
    }

    public static void i(String tag, String msg) {
        i("[" + tag + "]" + msg, INDEX_PRE_METHOD);
    }

    public static void i(String msg, int index) {
        if (!sDebug) {
            return;
        }
        msg = "[" + getStackTrace(index) + "]\n" + msg;

        int size = 3100;
        if (msg.length() > size) {
            boolean needPrefix = false;
            while (true) {
                if (msg.length() > size) {
                    String logStr = msg.substring(0, size);
                    Log.i(TAG, needPrefix ? "[接上文]" + logStr : logStr);
                    msg = msg.substring(size);
                } else {
                    Log.i(TAG, "[接上文]" + msg + "\n---------------------------------------------------------------");
                    break;
                }
                needPrefix = true;
            }
        } else {
            Log.i(TAG, msg);
        }

        if (sLogToFile) {
            String time = getCurrentFormattedTime();
            writeLocalLog(time, "_i: ", msg);
        }
    }

    public static void d(String msg) {
        d(msg, INDEX_PRE_METHOD);
    }

    public static void d(String tag, String msg) {
        d("[" + tag + "]" + msg, INDEX_PRE_METHOD);
    }

    public static void d(String msg, int index) {
        if (!sDebug) {
            return;
        }
        msg = "[" + getStackTrace(index) + "]\n" + msg;
        Log.d(TAG, msg);
        if (sLogToFile) {
            String time = getCurrentFormattedTime();
            writeLocalLog(time, "_d: ", msg);
        }
    }

    public static void w(String msg) {
        w(msg, INDEX_PRE_METHOD);
    }

    public static void w(String tag, String msg) {
        w("[" + tag + "]" + msg, INDEX_PRE_METHOD);
    }

    public static void w(String msg, int index) {
        if (!sDebug) {
            return;
        }
        msg = "[" + getStackTrace(index) + "]\n" + msg;
        Log.w(TAG, msg);
        if (sLogToFile) {
            String time = getCurrentFormattedTime();
            writeLocalLog(time, "_w: ", msg);
        }
    }

    public static void e(String msg) {
        e(msg, INDEX_PRE_METHOD);
    }

    public static void e(String tag, String msg) {
        e("[" + tag + "]" + msg, INDEX_PRE_METHOD);
    }

    public static void e(String msg, int index) {
        if (!sDebug) {
            return;
        }
        msg = "[" + getStackTrace(index) + "]\n" + msg;
        Log.e(TAG, "catchInfo------------->\n" + msg);
        if (sLogToFile) {
            String time = getCurrentFormattedTime();
            writeLocalLog(time, "_e: ", msg);
        }
    }

    private static String writeLocalLog(String time, String level, String msg) {
        if (sLogSaveDir == null) {
            Log.e(TAG, "LOCAL_LOG_DIR 路径异常, 无法写日志");
            return null;
        }
        Callable<String> task = new Callable<String>() {
            @Override
            public String call() throws Exception {
                String fileName = getCurrentDate() + ".txt";
                File file = new File(sLogSaveDir, fileName);
                writeLocalLog(file, time, level, msg);
                return time;
            }
        };
        addTask(task);
        return null;
    }

    private static volatile Future<String> lastSubmit = null;

    private static void addTask(Callable<String> task) {
        try {
            lastSubmit = sExecutor.submit(task);
        } catch (RejectedExecutionException ignored) {
            if (sDebug) {
                int size = sWorkQueue.size();
                Log.w(TAG, "任务添加失败,当前队列[" + size + "]");
            }

            try {
                lastSubmit.get();
            } catch (Exception e) {
                Log.w(TAG, Log.getStackTraceString(e));
            }

            addTask(task);
        } catch (Exception e) {
            Log.w(TAG, Log.getStackTraceString(e));
        }
    }

    private static void deleteOldestFile(File dir) {
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        File[] files = dir.listFiles();
        if (files != null && files.length > FILES_LENGTH) {
            Arrays.sort(files, new Comparator<File>() {
                @Override
                public int compare(File f1, File f2) {
                    return Long.compare(f1.lastModified(), f2.lastModified());
                }
            });
//            for (File file : files) {
//                Log.i(TAG, "after sort->" + file);
//            }

            for (int i = FILES_LENGTH; i < files.length; i++) {
                if (!files[i].delete()) {
                    Log.w(TAG, "delete.err->" + files[i]);
                }
            }
        }
    }

    private static void writeLocalLog(File file, String time, String level, String msg) {
        try (FileWriter fw = new FileWriter(file, true);
             PrintWriter pw = new PrintWriter(fw, true);) {
            pw.print(time);
            pw.print(level);
            pw.println(msg);
            pw.flush();
        } catch (Exception e) {
            Log.w(TAG, "writeLocalLog Error:" + e.getMessage());
        }
    }

    /**
     * 获取堆栈的方法名
     */
    private static String getStackTrace(int index) {
        StackTraceElement[] ses = Thread.currentThread().getStackTrace();
        if (index > -1 && index < ses.length) {
            return ses[index].toString();
        } else {
            return "OutOfRange: " + index + " for " + ses.length + "\n";
        }
    }

    private static String getCurrentDate() {
        /* 获取当前日期, _HH按小时存 */
        Date date = new Date(System.currentTimeMillis());
        return getSafeDateFormat("yyyy_MM_dd").format(date);
    }

    public static String getCurrentFormattedTime() {
        /*获取当前时间*/
        Date date = new Date(System.currentTimeMillis());
        return getSafeDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
    }

    public static void enableSaveLog(boolean enable) {
        sLogToFile = enable;
        createEx();
    }

    public static boolean isDebug() {
        return sDebug;
    }

    public static void showStackTrace() {
        if (!sDebug) {
            return;
        }
        StackTraceElement[] trace = Thread.currentThread().getStackTrace();
        StringBuilder sb = new StringBuilder("to showStackTrace->");
        for (StackTraceElement ste : trace) {
            sb.append(ste).append("\n");
        }
        i(sb.toString(), INDEX_PRE_METHOD);
    }
}